//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package idea_dao

import (
	"context"
	"regexp"

	"atomgit.com/openlinksaas/api-server/dao/db_ns"
	"atomgit.com/openlinksaas/proto-gen-go.git/project_idea_api"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type BasicIdeaDbItem struct {
	Title       string   `bson:"title" json:"title"`
	Content     string   `bson:"content" json:"content"`
	KeywordList []string `bson:"keywordList" json:"keywordList"`
}

type IdeaPermDbItem struct {
	UpdateForAll          bool     `bson:"updateForAll"`
	ExtraUpdateUserIdList []string `bson:"extraUpdateUserIdList"`
}

type IdeaDbItem struct {
	IdeaId        string          `bson:"_id"`
	ProjectId     string          `bson:"projectId"`
	BasicInfo     BasicIdeaDbItem `bson:"basicInfo"`
	AgreeValue    int32           `bson:"agreeValue"`
	AgreeCount    uint32          `bson:"agreeCount"`
	DisagreeCount uint32          `bson:"disagreeCount"`
	CreateUserId  string          `bson:"createUserId"`
	UpdateUserId  string          `bson:"updateUserId"`
	CreateTime    int64           `bson:"createTime"`
	UpdateTime    int64           `bson:"updateTime"`
	IdeaGroupId   string          `bson:"ideaGroupId"`
	IdeaPerm      IdeaPermDbItem  `bson:"ideaPerm"`
}

func (item *IdeaDbItem) ToIdea() *project_idea_api.Idea {
	return &project_idea_api.Idea{
		IdeaId: item.IdeaId,
		BasicInfo: &project_idea_api.BasicIdea{
			Title:       item.BasicInfo.Title,
			Content:     item.BasicInfo.Content,
			KeywordList: item.BasicInfo.KeywordList,
		},
		AgreeCount:    item.AgreeCount,
		DisagreeCount: item.DisagreeCount,
		CreateUserId:  item.CreateUserId,
		UpdateUserId:  item.UpdateUserId,
		CreateTime:    item.CreateTime,
		UpdateTime:    item.UpdateTime,
		IdeaGroupId:   item.IdeaGroupId,
		IdeaPerm: &project_idea_api.IdeaPerm{
			UpdateForAll:          item.IdeaPerm.UpdateForAll,
			ExtraUpdateUserIdList: item.IdeaPerm.ExtraUpdateUserIdList,
		},
	}
}

type _IdeaDao struct {
	mc *mongo.Collection
}

func newIdeaDao(db *mongo.Database) (*_IdeaDao, error) {
	mc := db.Collection(db_ns.DB_NS_PROJECT_IDEA + "idea")
	_, err := mc.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
		{
			Keys: bson.D{{"projectId", 1}, {"ideaGroupId", 1}, {"updateTime", 1}},
		},
		{
			Keys: bson.D{{"projectId", 1}, {"updateTime", 1}},
		},
		{
			Keys: bson.D{{"projectId", 1}, {"basicInfo.keywordList", 1}, {"agreeValue", 1}, {"updateTime", 1}},
		},
	})
	if err != nil {
		return nil, err
	}
	return &_IdeaDao{mc: mc}, nil
}

func (db *_IdeaDao) Insert(ctx context.Context, dbItem *IdeaDbItem) error {
	_, err := db.mc.InsertOne(ctx, dbItem)
	return err
}

func (db *_IdeaDao) Exist(ctx context.Context, projectId, ideaId string) (bool, error) {
	count, err := db.mc.CountDocuments(ctx, bson.M{"_id": ideaId, "projectId": projectId})
	return count > 0, err
}

func (db *_IdeaDao) Get(ctx context.Context, projectId, ideaId string) (*IdeaDbItem, error) {
	retItem := &IdeaDbItem{}
	err := db.mc.FindOne(ctx, bson.M{"_id": ideaId, "projectId": projectId}).Decode(retItem)
	return retItem, err
}

func (db *_IdeaDao) Remove(ctx context.Context, projectId, ideaId string) error {
	_, err := db.mc.DeleteOne(ctx, bson.M{"_id": ideaId, "projectId": projectId})
	return err
}

func (db *_IdeaDao) UpdateContent(ctx context.Context, projectId, ideaId, title, content, updateUserId string, updateTime int64) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": ideaId, "projectId": projectId},
		bson.M{"$set": bson.M{"basicInfo.title": title, "basicInfo.content": content, "updateUserId": updateUserId, "updateTime": updateTime}})
	return err
}

func (db *_IdeaDao) UpdateKeywordList(ctx context.Context, projectId, ideaId string, keywordList []string, updateUserId string, updateTime int64) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": ideaId, "projectId": projectId},
		bson.M{"$set": bson.M{"basicInfo.keywordList": keywordList, "updateUserId": updateUserId, "updateTime": updateTime}})
	return err
}

func (db *_IdeaDao) UpdateAgreeValue(ctx context.Context, projectId, ideaId string, agreeCount, disagreeCount uint32) error {
	agreeValue := int32(agreeCount) - int32(disagreeCount)
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": ideaId, "projectId": projectId},
		bson.M{"$set": bson.M{"agreeValue": agreeValue, "agreeCount": agreeCount, "disagreeCount": disagreeCount}})
	return err
}

func (db *_IdeaDao) UpdateIdeaPerm(ctx context.Context, projectId, ideaId string, dbItem *IdeaPermDbItem, updateUserId string, updateTime int64) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": ideaId, "projectId": projectId},
		bson.M{"$set": bson.M{"ideaPerm": dbItem, "updateUserId": updateUserId, "updateTime": updateTime}})
	return err
}

func (db *_IdeaDao) UpdateIdeaGroupId(ctx context.Context, projectId, ideaId, ideaGroupId string) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": ideaId, "projectId": projectId},
		bson.M{"$set": bson.M{"ideaGroupId": ideaGroupId}})
	return err
}

func (db *_IdeaDao) Count(ctx context.Context, projectId string, listParam *project_idea_api.ListIdeaParam) (uint32, error) {
	query := db.genListQuery(projectId, listParam)
	count, err := db.mc.CountDocuments(ctx, query)
	return uint32(count), err
}

func (db *_IdeaDao) List(ctx context.Context, projectId string, listParam *project_idea_api.ListIdeaParam,
	sortType project_idea_api.IDEA_SORT_TYPE, offset, limit int64) ([]*IdeaDbItem, error) {
	query := db.genListQuery(projectId, listParam)
	sortParam := bson.D{}
	if sortType == project_idea_api.IDEA_SORT_TYPE_IDEA_SORT_UPDATE_TIME {
		sortParam = bson.D{{"updateTime", -1}}
	} else if sortType == project_idea_api.IDEA_SORT_TYPE_IDEA_SORT_APPRAISE {
		sortParam = bson.D{{"agreeValue", -1}, {"updateTime", -1}}
	}
	cursor, err := db.mc.Find(ctx, query, options.Find().SetSkip(offset).SetLimit(limit).SetSort(sortParam))
	if err != nil {
		return nil, err
	}
	retList := []*IdeaDbItem{}
	err = cursor.All(ctx, &retList)
	return retList, err
}

func (db *_IdeaDao) genListQuery(projectId string, listParam *project_idea_api.ListIdeaParam) bson.M {
	query := bson.M{"projectId": projectId}
	if listParam.FilterByKeyword {
		if listParam.KeywordSearchType == project_idea_api.KEYWORD_SEARCH_TYPE_KEYWORD_SEARCH_AND {
			query["basicInfo.keywordList"] = bson.M{"$all": listParam.KeywordList}
		} else if listParam.KeywordSearchType == project_idea_api.KEYWORD_SEARCH_TYPE_KEYWORD_SEARCH_OR {
			query["basicInfo.keywordList"] = bson.M{"$in": listParam.KeywordList}
		}
	}
	if listParam.FilterByGroupId {
		query["ideaGroupId"] = listParam.GroupId
	}
	if listParam.FilterByTitleKeyword {
		query["basicInfo.title"] = primitive.Regex{
			Pattern: regexp.QuoteMeta(listParam.TitleKeyword),
			Options: "i",
		}
	}
	return query
}

func (db *_IdeaDao) ListAllKeyword(ctx context.Context, projectId string) ([]string, error) {
	itemList, err := db.mc.Distinct(ctx, "basicInfo.keywordList", bson.M{"projectId": projectId})
	if err != nil {
		return nil, err
	}
	retList := []string{}
	for _, item := range itemList {
		retList = append(retList, item.(string))
	}
	return retList, nil
}

func (db *_IdeaDao) ListExistKeyword(ctx context.Context, projectId string, keywordList []string) ([]string, error) {
	if len(keywordList) == 0 {
		return []string{}, nil
	}
	itemList, err := db.mc.Distinct(ctx, "basicInfo.keywordList", bson.M{"projectId": projectId, "basicInfo.keywordList": bson.M{"$in": keywordList}})
	if err != nil {
		return nil, err
	}
	retList := []string{}
	for _, item := range itemList {
		retList = append(retList, item.(string))
	}
	return retList, nil
}

func (db *_IdeaDao) DistinctIdeaIdByProject(ctx context.Context, projectId string) ([]string, error) {
	itemList, err := db.mc.Distinct(ctx, "_id", bson.M{"projectId": projectId})
	if err != nil {
		return nil, err
	}
	retList := []string{}
	for _, item := range itemList {
		retList = append(retList, item.(string))
	}
	return retList, nil
}

// 只在升级脚本中调用
func (db *_IdeaDao) FixIdeaGroup(ctx context.Context, projectId, ideaGroupId string) error {
	_, err := db.mc.UpdateMany(ctx, bson.M{"projectId": projectId}, bson.M{"$set": bson.M{"ideaGroupId": ideaGroupId}})
	return err
}
